/* $HeadURL$
* $Id$
*
* Copyright (c) 2009-2010 DuraSpace
* http://duraspace.org
*
* In collaboration with Topaz Inc.
* http://www.topazproject.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.akubraproject.rmi.server;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.akubraproject.BlobStore;
import org.akubraproject.BlobStoreConnection;
import org.akubraproject.rmi.remote.RemoteConnection;
import org.akubraproject.rmi.remote.RemoteSynchronization;
import org.akubraproject.rmi.remote.RemoteTransactionListener;
import org.akubraproject.rmi.remote.RemoteXAResource;
/**
* A transaction wrapper for use in akubra-rmi-server side that forwards calls to the
* akubra-rmi-client side Transaction.
*
* @author Pradeep Krishnan
*/
public class ServerTransactionListener extends UnicastExportable
implements Transaction, RemoteTransactionListener {
private static final Logger log = LoggerFactory.getLogger(ServerTransactionListener.class);
private static final long serialVersionUID = 1L;
// For call forwarding
private final SynchronousQueue<Operation<?>> operations = new SynchronousQueue<Operation<?>>();
private final SynchronousQueue<Result<?>> results = new SynchronousQueue<Result<?>>();
// For isSameRM support
private final Map<XAResource, ServerXAResource> xas = new HashMap<XAResource, ServerXAResource>();
/**
* Creates a new ServerTransactionListener
*
* @param store the store to open a connection to
* @param hints A set of hints for openConnection
* @param exporter exporter for exporting XAResource and Synchronization
* @throws RemoteException on an error in export
*/
public ServerTransactionListener(final BlobStore store, final Map<String, String> hints,
Exporter exporter) throws RemoteException {
super(exporter);
Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "akubra-rmi-open-connection");
t.setDaemon(true);
return t;
}
}).submit(new Runnable() {
public void run() {
try {
openConnection(store, hints);
} catch (Throwable t) {
log.warn("Uncaught exception in open-connection", t);
unExport(false);
}
}
});
}
private void openConnection(BlobStore store, Map<String, String> hints) {
Result<RemoteConnection> result;
ServerConnection con = null;
try {
BlobStoreConnection res = store.openConnection(this, hints);
con = new ServerConnection(res, getExporter());
result = new Result<RemoteConnection>(con);
} catch (Throwable t) {
if (log.isDebugEnabled())
log.debug("openConnection failed. Sending an error result ...", t);
result = new Result<RemoteConnection>(t);
}
try {
operations.put(result);
} catch (Throwable t) {
log.warn("Failed to send results of openConnection back to client", t);
try {
if (con != null)
con.close();
} catch (Throwable e) {
log.warn("Failed to close", e);
}
} finally {
/*
* Note with posting of the openConnection results we are done and
* so this object can be unExported. Because of the SynchronousQueue
* usage we know that the client side has received the results. But
* in case the put(result) failed, the unExport will cause the client
* to abort.
*/
unExport(false);
}
}
public Operation<?> getNextOperation() throws InterruptedException, RemoteException {
if (log.isDebugEnabled())
log.debug("getting next operation ....");
return operations.take();
}
public void postResult(Result<?> result) throws InterruptedException, RemoteException {
if (log.isDebugEnabled())
log.debug("posting a result ....");
results.put(result);
if (log.isDebugEnabled())
log.debug("finished posting a result ....");
}
@SuppressWarnings("unchecked")
private <T> T executeOnClient(Operation<T> operation)
throws ExecutionException, SystemException {
if (getExported() == null)
throw new IllegalStateException("No longer referenced by any clients.");
try {
if (log.isDebugEnabled())
log.debug("posting an operation ....");
operations.put(operation);
if (log.isDebugEnabled())
log.debug("finished posting an operation ....");
} catch (InterruptedException e) {
if (log.isDebugEnabled())
log.debug("interrupted while posting an operation ....", e);
throw (SystemException) new SystemException("interrupted while posting an operation")
.initCause(e);
}
if (getExported() == null)
throw new IllegalStateException("No longer referenced by any clients.");
Result<?> result;
try {
if (log.isDebugEnabled())
log.debug("waiting for result...");
result = results.take();
if (log.isDebugEnabled())
log.debug("got a result");
} catch (InterruptedException e) {
if (log.isDebugEnabled())
log.debug("interrupted while waiting for a result ....", e);
throw (SystemException) new SystemException("interrupted while waiting for a result")
.initCause(e);
}
if (getExported() == null)
throw new IllegalStateException("No longer referenced by any clients.");
return (T) result.get();
}
public void commit()
throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
SecurityException, IllegalStateException, SystemException {
throw new SecurityException("Cannot commit from this JVM. Transaction is on remote client.");
}
public boolean delistResource(XAResource xaRes, int flags)
throws IllegalStateException, SystemException {
ServerXAResource xa = xas.get(xaRes);
if (xa == null)
return false;
boolean ret;
try {
ret = executeOnClient(new DelistXAResource(xa, flags));
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof SystemException)
throw (SystemException) cause;
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
throw new RuntimeException("Error reported by server", cause);
}
if (ret)
xas.remove(xaRes);
return ret;
}
public boolean enlistResource(XAResource xaRes)
throws RollbackException, IllegalStateException, SystemException {
if (xaRes == null)
return false;
ServerXAResource xa;
try {
xa = new ServerXAResource(xaRes, null, getExporter());
} catch (RemoteException e) {
throw (SystemException) new SystemException("Failed to export XAResource").initCause(e);
}
boolean ret;
try {
ret = executeOnClient(new EnlistXAResource(xa));
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RollbackException)
throw (RollbackException) cause;
if (cause instanceof SystemException)
throw (SystemException) cause;
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
throw new RuntimeException("Error reported by server", cause);
}
if (ret)
xas.put(xaRes, xa);
return ret;
}
public int getStatus() throws SystemException {
try {
return executeOnClient(new GetStatus());
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof SystemException)
throw (SystemException) cause;
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
throw new RuntimeException("Error reported by server", cause);
}
}
public void registerSynchronization(Synchronization sync)
throws RollbackException, IllegalStateException, SystemException {
RemoteSynchronization rsync;
try {
rsync = new ServerSynchronization(sync, getExporter());
} catch (RemoteException e) {
throw (SystemException) new SystemException("Failed to export Synchronization").initCause(e);
}
try {
executeOnClient(new RegisterSynchronization(rsync));
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RollbackException)
throw (RollbackException) cause;
if (cause instanceof SystemException)
throw (SystemException) cause;
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
throw new RuntimeException("Error reported by server", cause);
}
}
public void rollback() throws IllegalStateException, SystemException {
try {
executeOnClient(new Rollback());
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof SystemException)
throw (SystemException) cause;
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
throw new RuntimeException("Error reported by server", cause);
}
}
public void setRollbackOnly() throws IllegalStateException, SystemException {
throw new IllegalStateException("Cannot change the transaction state from this JVM. "
+ "Transaction is on remote client.");
}
/**
* Gets the akubra-rmi-server side XAResource for the given remote stub.
*
* @param res the remote stub.
*
* @return a corresponding enlisted XAResource
*/
public XAResource getXAResource(RemoteXAResource res) {
if (res == null)
return null;
for (Map.Entry<XAResource, ServerXAResource> e : xas.entrySet())
if (res.equals(e.getValue()) || res.equals(e.getValue().getExported()))
return e.getKey();
return null;
}
}